home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 7
/
Apprentice-Release7.iso
/
Source Code
/
Pascal
/
Snippets
/
PNL Libraries
/
NeoTextBox.p
< prev
next >
Wrap
Text File
|
1997-03-25
|
12KB
|
327 lines
unit NeoTextBox;
{ Written in C by Bryan K. Ressler (Beaker), from Develop issue 9 }
interface
uses
Types;
const
ntbJustFull = 128;
procedure NeoTextBox (theText: Ptr; textLen: longint; wrapBox: Rect; align: integer; lhCode: integer; var endY: integer; var lhUsed: integer; var lineCount: integer);
implementation
uses
Script, FixMath, Fonts, TextEdit, TextUtils, MyLowLevel, MyMathUtils;
const
kReturnChar = 13;
{}
{* NTBLineHeight - figures line height}
{*}
{* Input: theText the entire text that was given to the NeoTextBox call}
{* textLen the length in bytes of the text}
{* lhCode the line height code that was passed to NeoTextBox}
{* startY VAR - we return the starting vertical pen location here}
{*}
{* Output: returns the line height to use}
function NTBLineHeight (theText: Ptr; textLen: longint; wrapBox: Rect; lhCode: integer; var startY: integer): integer;
const
kTrueTypeTrap = $54;
kUnimplTrap = $9f;
var
asc, desc: integer; { Used in the OutlineMetrics calls }
fInfo: FontInfo; { The old-style font information record }
frac: Point; { The fraction for the TrueType calls }
lineHeight: integer; { The return value }
hasTrueType: boolean;
err: OSErr;
begin
GetFontInfo(fInfo);
if lhCode < 0 then begin
{ If the user has specified variable-height lines, we need to try}
{ to determine the tallest ascent in the given text. We can only}
{ really do this if the font is a TrueType font. Otherwise, we}
{ punt and use old-fashioned GetFontInfo numbers.}
frac.h := 1;
frac.v := 1;
hasTrueType := NGetTrapAddress(kTrueTypeTrap, ToolTrap) <> NGetTrapAddress(kUnimplTrap, ToolTrap);
if hasTrueType & IsOutline(frac, frac) then begin
{ At this Point we know the current font is a TrueType font, so}
{ we do an OutlineMetrics call with our full text. It will put}
{ the tallest character ascent into asc, and the deepest descent}
{ into desc. Then we choose between whichever's most between}
{ the old-style ascent/descent and the numbers we get from}
{ the OutlineMetrics call.}
{ }
err := OutlineMetrics(textLen, theText, frac, frac, asc, desc, nil, nil, nil);
end
else begin
{ At this Point we know the current font isn't TrueType, so we}
{ just use the old way of calculating line height.}
err := -1;
end;
if err <> noErr then begin
asc := 0;
desc := 0;
end;
lineHeight := Max(fInfo.ascent, asc) + Max(fInfo.descent, -desc) + fInfo.leading;
startY := wrapBox.top + Max(fInfo.ascent, asc) + fInfo.leading;
end
else if lhCode = 0 then begin
{}
{ If the user has specified "default" line height, he just wants us}
{ to get the line height from the FontInfo record.}
{ }
lineHeight := fInfo.ascent + fInfo.descent + fInfo.leading;
startY := wrapBox.top + fInfo.ascent + fInfo.leading;
end
else begin
{ If the user has provided a specific line height, we just trust}
{ them. We can't really generate too good of a starting vertical}
{ coordinate, but we munge one together anyway.}
{ }
lineHeight := lhCode;
startY := wrapBox.top + lhCode + fInfo.leading;
end;
NTBLineHeight := lineHeight;
end;
{}
{* NTBDraw - draws a line with appropriate justification}
{*}
{* Input: breakCode the break code that was returned from StyledLineBreak}
{* lineStart pointer to the beginning of the text for the current line}
{* lineBytes the length in bytes of the the text for this line}
{* wrapBox the box within which we're wrapping}
{* align the text alignment as specified by the user}
{* curY our current vertical pen coordinate}
{* boxWidth the width of wrapBox (since NeoTextBox already calculated it)}
{*}
{* Output: none (draws on the screen)}
{}
procedure NTBDraw (breakCode: StyledLineBreakCode; lineStart: Ptr; lineBytes: longint; wrapBox: Rect; align: integer; curY: integer; boxWidth: integer);
var
blackLen: longint; { Length of non-white characters }
slop: integer; { Number of pixels of slop for full just }
begin
{}
{ The first thing we do here is determine the length of the "black" part}
{ of the current line. This excludes spaces, carriage returns, and other}
{ white stuff depending on the language. How do we know what to elim-}
{ inate? We DON'T! So we ask our friend the Script Manager to do it}
{ for us. VisibleLength returns the number of bytes that we should use}
{ for pixel width calculations.}
{ }
blackLen := VisibleLength(lineStart, lineBytes);
if align = ntbJustFull then begin
{}
{ For full justification, we need to calculate the "slop" space on}
{ the line that's not filled up by the text. Then we move to the}
{ margin and get ready to draw BUT WAIT! If this is the last line of}
{ a paragraph, we need to draw it with whatever the system justifi-}
{ cation is. So if the break code indicates we're at the end of the}
{ text, or we can find a carriage return there ourselves, we just}
{ change the text alignment to the current default system text align-}
{ ment and fall through without drawing. If it's just another line}
{ within a paragraph, we use the handy Script Manager routine DrawJust}
{ to draw it justified. In languages like Arabic, full justification}
{ is performed differently than just spacing out the words. DrawJust}
{ handles cases like these correctly.}
{ }
{ Note that when we go looking for the carriage return at the end of}
{ the line, it's okay to simply index to the last Byte of the string.}
{ This is because the carriage return character, 0x0d, is in the}
{ control-code range, which is defined to never be the low Byte of a}
{ two Byte character.}
{ }
slop := boxWidth - TextWidth(lineStart, 0, blackLen);
MoveTo(wrapBox.left, curY);
if (breakCode = smBreakOverflow) | (AddPtrLong(lineStart, lineBytes - 1)^ = kReturnChar) then begin
align := GetSysDirection;
end
else begin
DrawJust(lineStart, blackLen, slop);
end;
end;
{}
{ For the rest of the text alignments (left, center, and right), we just}
{ move the pen to the right place and draw the text with DrawText. Note}
{ that the alignments that could have come into the NeoTextBox call have}
{ been resoved down to one of the four constants teForceLeft, teJustRight}
{ teJustCenter, or ntbJustFull.}
{ }
case align of
teForceLeft, teJustLeft:
MoveTo(wrapBox.left, curY);
teJustRight:
MoveTo(wrapBox.right - TextWidth(lineStart, 0, blackLen), curY);
teJustCenter:
MoveTo(wrapBox.left + (boxWidth - TextWidth(lineStart, 0, blackLen)) div 2, curY);
otherwise
;
end;
if align <> ntbJustFull then begin
DrawText(lineStart, 0, lineBytes);
end;
end;
{}
{* NeoTextBox - word-wraps text inside a given box}
{*}
{* Input: theText the text we need to wrap}
{* textLen the length in bytes of the text}
{* wrapBox the box within which we're wrapping}
{* align the text alignment}
{* teForceLeft, teFlushLeft left justified}
{* teJustCenter, teCenter center justified}
{* teJustRight, teFlushRight right justified}
{* ntbJustFull full justified}
{* teJustLeft, teFlushDefault system justified}
{* lhCode the line height code that was passed to NeoTextBox}
{* < 0 variable - based on tallest character}
{* 0 default - based on GetFontInfo}
{* > 0 fixed - use lhCode as the line height}
{* endY VAR - if non-nil, the vertical coord of the last line}
{* lhUsed VAR - if non-nil, the line height used to draw the text}
{*}
{* Output: returns the number of line drawn total (even if they drew outside of}
{* the boundries of wrapBox)}
{}
procedure NeoTextBox (theText: Ptr; textLen: longint; wrapBox: Rect; align: integer; lhCode: integer; var endY: integer; var lhUsed: integer; var lineCount: integer);
var
oldClip: RgnHandle; { Saved clipping region }
breakCode: StyledLineBreakCode; { Returned code from StyledLineBreak }
fixedMax: Fixed; { boxWidth converted to fixed Point }
wrapWid: Fixed; { Width to wrap to }
boxWidth: integer; { Width of the wrapBox }
lineBytes: longint; { Number of bytes in one line }
lineHeight: integer; { Calculated line height }
curY: integer; { Current vertical pen location }
textLeft: longint; { Pointer to remaining bytes of text }
lineStart: Ptr; { Pointer to beginning of a line }
textEnd: Ptr; { Pointer to the end of input text }
begin
{}
{ First, we save the old clipping region and clip to wrapBox. Then, figure}
{ the width of wrapBox, and make a fixed Point version of it. Also, resolve}
{ the text alignment teFlushDefault to be the default system text alignment.}
{ }
oldClip := NewRgn;
GetClip(oldClip);
ClipRect(wrapBox);
boxWidth := wrapBox.right - wrapBox.left;
fixedMax := Long2Fix(boxWidth);
if align = teFlushDefault then begin
align := GetSysDirection;
end;
{}
{ Now we call NTBLineHeight to calculate the appropriate line height. It}
{ also figures our starting vertical pen location in curY based on the}
{ line height and other metric parameters. Clear lineCount, set}
{ lineStart to Point to the beginning of the text, calculate textEnd for}
{ comparison to know when we're done, and preset textLeft to be all the}
{ text.}
{ }
lineHeight := NTBLineHeight(theText, textLen, wrapBox, lhCode, curY);
lineCount := 0;
lineStart := theText;
textEnd := AddPtrLong(theText, textLen);
textLeft := textLen;
{}
{ This is the main wrap-and-draw loop. I bet you never thought wrapping}
{ text could be so easy...}
{ }
while textLeft > 0 do begin
{}
{ Every line, we have to preset lineBytes to something non-zero.}
{ This tells StyledLineBreak that we're drawing the first format}
{ run on the line (of course, for us, there's only ONE format run}
{ total). Also preset wrapWid. StyledLineBreak will always modify}
{ lineBytes (to tell you how many bytes are on this line), and will}
{ modify wrapWid, so we have to reset them each line.}
{ }
lineBytes := 1;
wrapWid := fixedMax;
breakCode := StyledLineBreak(lineStart, textLeft, 0, textLeft, 0, wrapWid, lineBytes);
{}
{ Now that the Script Manager has done all the really hard work for}
{ us, we draw the line. We already knew lineStart, StyledLineBreak}
{ gave us lineBytes, which we pass to NTBDraw. It'll Handle the}
{ different text alignments itself.}
{ }
NTBDraw(breakCode, lineStart, lineBytes, wrapBox, align, curY, boxWidth);
{}
{ Now we advance our vertical position down by the height of one}
{ line, advance lineStart by the number of bytes we just drew,}
{ calculate a new textLeft, and increment our line count.}
{ }
curY := curY + lineHeight;
lineStart := AddPtrLong(lineStart, lineBytes);
textLeft := textLeft - lineBytes;
lineCount := lineCount + 1;
end;
{}
{ Well that was a job well done. Let's return some useful values, too.}
{ If the user gave pointers for endY and lhUsed, we stuff our ending}
{ vertical coordinate and the line height we calculated into those,}
{ respectively. These allow the guy to put something after the wrapped}
{ text (or at least know the right place to put it).}
{ }
endY := curY - lineHeight;
lhUsed := lineHeight;
{}
{ Finally, restore the clipping region, dispose of the region, and}
{ return the TOTAL number of lines drawn (note that we didn't stop}
{ drawing when curY advanced past wrapBox->bottom. This way, the user}
{ could tell that the text overflowed wrapBox. If you want to know how}
{ many lines fit, divide wrapBox by lhUsed. This way, you get the best}
{ of both worlds.}
{ }
SetClip(oldClip);
DisposeRgn(oldClip);
end;
end.